/* 网络流
* 1.概念
    流网络：就是有向边和点集，有向边有容量
    可行流：满足容量限制和流量守恒的流
    可行流的流量：为从源点流出的流量或者流入汇点的流量
                从源点流出的流量 - 流入源点的流量
                即 |f| = f[s, V]  - f[V, s]
    最大流：具有最大流量的可行流
    f(x, y) 是指: 从起点 x 流向 y 的流量

* 2.残留网络
    残留网络：此概念是针对可行流来定义的。不同可行流都对应着一个残留网络。
            对于残留网络，V’ = V, E’= E + E的所有反向边。
            其中，正向边的容量=原边容量 - 原边流量，反向边的容量是原边流量。
            残留网络也是一张图，残留网络有反向边，原图没有反向边
            残留网路, 考虑反向边。
    残留网络的可行流f' + 原图的可行流f = 原图的另一个可行流

    残留网络中的边定义: C'(u, v) = C(u, v) - 原图中的f(u,v) ;
    残留网络中的反向边: C'(v, u) = 原图中的f(u, v);
    现有流网络中的一个可行流f，对于其对应残留网络的一个可行流f’，有f + f’仍是原流网络中的一个可行流。而且|f + f’| = |f| + |f’|
    u 和 v 只是 图中任意的两个点;C(u，v) 代表原图中 从u到v这条边的容量是多少
        C'(u, v) 代表在残留网络中 从 u到v 这条边的容量 
    如果当前流是最大流，则一定不存在增广网络，但如果不存在增广网络，当前流未必是最大流(局部最优解)

    对于一个可行流f，如果能找到其对应残留网络中的一个可行流f’，那么就可以得到一个流量更大的可行流。不断迭代，就可以得到最大流

* 3.增广路径
    增广路径: 残留网络中一条从源点到汇点的路，这条路上的边的容量都大于0，即f'>0
             如果f的残留网络中有增广路径，则f不是最大流，流量仍可继续增加, 如果不存在，则f是最大流
  
* 4.割
    将点集分为两个部分S，T，其中源点s∈S，汇点t∈T。
    割的容量：c(S, T) = Σc(u, v), u∈S, v∈T 割边集的容量和，割的流量 只与割的划分有关
    割的流量：f(S, T) = ∑f(u, v) - ∑f(v, u), u∈S, v∈T  割边集的正向流量-割边集的反向流量
            对于不同的可行流，计算的流量是不一样的
    定理1：f(S, T) <= c(S, T)，即割的流量 <= 割的容量
    定理2：|f| = f(S, T)，即任意可行流f的流量等于任意割的流量
    定理3：|f| <= c(S, T)，即任意可行流的流量 <= 任意割的容量
    定理4：最大流最小割定理--最大流 = 最小割

* 5.算法思路
    (1)EK算法 O(nm^2) n:[1000,10000]
        邻接表存图，需要快速找到反向边，正向反向边连续加，i^1即为i边的反向边
        while()
        {
            1.找增广路径 f'
            2.更新残留网络 
        }
    (2)dinic算法 O(n^2m) n:[10000,100000]

* 本题:
      
           
*/
#define DEBUG
#pragma GCC optimize("O1,O2,O3,Ofast")
#pragma GCC optimize("no-stack-protector,unroll-loops,fast-math,inline")
#pragma GCC target("avx,avx2,fma")
#pragma GCC target("sse,sse2,sse3,sse4,sse4.1,sse4.2,ssse3")

#include <iostream>
#include <cstring>
using namespace std;
#define int long long
const int N = 1e3 + 10, M = 1e4*2+10, INF = 0x3f3f3f3f;

int n, m, S, T;
int h[N], e[M], ne[M], f[M], idx; //f表示容量
int q[N], d[N], pre[N]; 
// d[i]: 从S点走到i点的路径中，边权的最小值
// pre[i]：当前这个点是从哪条边转移过来的

bool st[N];

void AddEdge(int a , int b , int c) 
{
    e[idx] = b , f[idx] = c , ne[idx] = h[a] , h[a] = idx ++;//a-(k)->b
    e[idx] = a , f[idx] = 0 , ne[idx] = h[b] , h[b] = idx ++;//b-(0)->a, 反向边
}

bool bfs()
{
    int hh = 0, tt = -1;
    memset(st, 0, sizeof st);
    q[++tt] = S, st[S] = true, d[S] = INF;
    while(hh <= tt)
    {
        int u = q[hh++];
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if(!st[v] && f[i]) //v未访问且i还有容量可走
            {
                //printf("u%d-(%d)->v%d\n", u, f[i], v);
                st[v] = true;
                d[v] = min(d[u], f[i]); //边权最小值
                pre[v] = i; //前驱边
                if(v == T) return true;
                q[++tt] = v;
            }
        }
    }
    return false;
}

int EK() {
    int r = 0; //最大流的数值
    while(bfs()) {
        r += d[T]; //S->T的路径上的边权最小值
        //printf("cur[r]:%d\n", r);
        for(int i = T; i != S; i=e[pre[i]^1]) { //T->S的边 -》 S->T的反向边
                                //v->u的反向边的起点
            f[pre[i]] -= d[T]; //入边使用d[T]流量
            f[pre[i]^1] += d[T]; //反向边还可多回退d[T]流量
            /*最初选择的增广路径可能不是最优的。
              通过增加反向边的容量，当后续找到其他增广路径时，
              可以利用反向边将之前选择的路径上的流量 “抵消” 一部分，
              重新分配流量，从而找到更优的解
              当前有争议的边的流量=正向流量-反向流量

              在bfs的if(f[i])中去掉已经分配的路径
              在d[v] = min(d[u], f[i])中找到可以抵消的流量
            */
           //printf("%d-(%d)->%d\n", e[pre[i]^1], d[T], e[pre[i]]);
        }

    }
    return r;
}

signed main()
{
    #ifdef DEBUG
        freopen("./in.txt", "r", stdin);
    #else
        ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    #endif

    cin >> n >> m >> S >> T;
    memset( h , -1 , sizeof h );
    while( m -- ) 
    {
        int a, b, c; 
        cin >> a >> b >> c;
        AddEdge(a, b, c);
    }
    cout << EK() << '\n';
  
    return 0;
}